<template>
  <div class="audition7">
    <h3>先从prototype起源说起</h3>
    <p>推荐阅读阮一峰老师的文章：<a href="http://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.html" target="_blank">Javascript继承机制的设计思想</a></p>
    <p>举个例子:</p>
    <code class="code">
      function People(name) {<br />
        <span class="indient">this.name = name</span><br />
        <span class="indient">this.type = 'human'</span><br />
      }
    </code>
    <p>我们创建了一个构造函数<span class="color">People</span>来表达人类这个概念</p>
    <p>此时，有两个人，张三和李二狗</p>
    <p>我们就用<span class="color">new</span>构造符来创建这两个人的实例</p>
    <code class="code">
      var zhangsan = new People('张三')<br />
      var liergou = new People('李二狗')<br />
      console.log(zhangsan)<br />
      console.log(liergou)
    </code>
    <p>输出结果：</p>
    <code class="code">
      {name: '张三', type: 'human'}<br />
      {name: '李二狗', type: 'human'}
    </code>
    <p>此时，李二狗大喊一声说，老子不做人了！于是，他把自己的类型改成了狗子！</p>
    <code class="code">
      liergou.type = '狗子'<br />
      console.log(liergou)
    </code>
    <p>输出结果：</p>
    <code class="code">
      {name: '李二狗', type: '狗子'}
    </code>
    <p>此时，张三就想，咱俩同样都是人类这个type，你把type改了，我张三也想做狗子，我不改type，我也是狗子了吧？</p>
    <p>查看一下：</p>
    <code class="code">
      console.log(zhangsan)
    </code>
    <p>显然，从输出结果中，就看到事情没那么简单：</p>
    <code class="code">
      {name: '张三', type: 'human'}
    </code>
    <p>张三就想，我怎么还是人类呢？？？</p>
    <p>由上述例子可知，构造函数中属性的值并不是共享的</p>
    <p>当一个实例修改了自己属性的值，并不会影响其他实例获取到原有的值</p>
    <p>那么，怎么才能做到共享呢？</p>
    <p>于是，js设计者就想到了一个方法，用prototype对象来存储共享的属性和方法</p>
    <p>和构造函数中不能共享的属性和方法区分开</p>
    <p>这样，如果所有的实例的值都有共同的变化，就直接去修改prototype中的属性值即可</p>
    <p>好了，我们来改造一下原来的例子，把共享的type放到prototype对象中：</p>
    <code class="code">
      function People(name) {<br />
        <span class="indient">this.name = name</span><br />
      }<br />
      People.prototype.type = 'human'<br />
      var zhangsan = new People('张三')<br />
      var liergou = new People('李二狗')<br />
      console.log(zhangsan, zhangsan.type)<br />
      console.log(liergou, liergou.type)
    </code>
    <p>输出结果：</p>
    <code class="code">
      {name: '张三'}&nbsp;'human'<br />
      {name: '李二狗'}&nbsp;'human'
    </code>
    <p>从打印结果也能看出，打印出来的实例对象中没有包含type这个属性。type的值是单独打印的</p>
    <p>当张三和李二狗都要做狗子时，直接去修改prototype中的值：</p>
    <code class="code">
      People.prototype.type = '狗子'<br />
      console.log(zhangsan, zhangsan.type)<br />
      console.log(liergou, liergou.type)
    </code>
    <p>输出结果：</p>
    <code class="code">
      {name: '张三'}&nbsp;'狗子'<br />
      {name: '李二狗'}&nbsp;'狗子'
    </code>
    <p>好了，总结一下：</p>
    <p>
      <span class="indient">1. prototype存在于函数中，是一个共享对象</span><br />
      <span class="indient">2. prototype中的属性和方法可以让所有实例调用</span><br />
      <span class="indient">3. 修改prototype中的属性和方法，所有实例调用时也会是改变后的值</span>
    </p>
    <hr>
    <h3>再来说说__proto__和原型链</h3>
    <p>推荐阅读：<a href="https://www.bookstack.cn/read/es6-3rd/spilt.4.docs-class-extends.md" target="_blank">原型和原型链1</a></p>
    <p>推荐阅读：<a href="https://blog.csdn.net/tuzi007a/article/details/114298065" target="_blank">原型和原型链2</a></p>
    <p>__proto__提供了一条查询链，规则如下：</p>
    <p>
      <span class="indient">1. 子类的__proto__指向父类，子类的prototype指向父类的实例</span><br />
      <span class="indient">2. 实例的__proto__指向构造函数的prototype</span><br />
      <span class="indient">3. 构造函数的prototy的__proto__指向Object的prototype</span><br />
      <span class="indient">4. Object的__proto__指向null</span>
    </p>
    <p>图例展示：</p>
    <img src="@images/prototype1.png" alt="" class="img">
    <p>这张图就表达了原型链的存在形式</p>
    <hr>
    <h3>下面再来说说实现继承的几种方式</h3>
    <p>这些方法之间也存在优劣，主要区别在以下几点：</p>
    <p>
      <span class="indient">父类被调用了几次</span><br />
      <span class="indient">子类实例能不能给父类传参数</span><br />
      <span class="indient">子类实例是否能调用父类原型上的属性和方法</span><br />
      <span class="indient">子类实例调用父类引用数据类型时，数据是否共享</span><br />
      <span class="indient">子类实例是否为父类的实例</span>
    </p>
    <p>1. 原型继承</p>
    <p>根据上述的第一条规则，子类和父类的关系，我们可以在两个构造函数之间建立一种继承关系</p>
    <p>即创建一个构造函数，然后让构造函数的prototype指向被继承的构造函数的实例</p>
    <p>代码举例：</p>
    <v-md-preview :text="text1" />
    <p>缺点：在父类中的复杂数据类型的属性(例子中的this.art)，子类实例1修改了该属性的值，那么子类实例2也会受到影响</p>
    <p>原因是父类中的复杂数据类型属性的引用被保存在了实例中</p>
    <p>不过这个缺点可以解决，即把复杂数据类型的值改成复杂数据类型的深拷贝即可，比如</p>
    <code class="code">
      把例子中的this.art = [1, 2]<br />
      改成<br />
      this.art = [1, 2].concat()即可
    </code>
    <p>2. call继承</p>
    <v-md-preview :text="text2" />
    <p>缺点：</p>
    <p>
      <span class="indient">子类实例不能调用父类原型上的属性和方法</span><br />
      <span class="indient">子类实例不是父类的实例</span>
    </p>
    <p>3. 原型+call组合继承</p>
    <v-md-preview :text="text3" />
    <p>组合继承拥有了所有优点，唯一不足之处在于，父类被调用了两次</p>
    <p>4. 寄生继承(copy类继承)</p>
    <v-md-preview :text="text4" />
    <p>优点是可以在父类的基础上添加属性和方法</p>
    <p>缺点很明显，没有用到原型，没有实例化等</p>
    <p>5. 寄生组合继承(原型+call+寄生)</p>
    <v-md-preview :text="text5" />
    <p>该方法集合了所有优点，是extends经过babel转译为es5的基本写法</p>
    <p>6. class 类的extends继承 </p>
    <p>该方法就是用寄生组合继承实现的</p>
    <code class="code">
      class Star {}<br />
      class Dili extends Star {}
    </code>
  </div>
</template>

<script>
const prefix = '``` js'
const suffix = '```'
export default {
  name: 'Audition7',
  data() {
    return {
      text1: `${prefix}
        function Star(age) {
          this.age = age
          this.art = [1, 2]
        }
        Star.prototype.sing = 'hello'
        function Dili(age) {
          this.age = age
        }
        Dili.prototype = new Star(18)
        
        var dili = new Dili(20)
        console.log(dili) // {age: 20}

        // 验证一下上面5条区别：
        console.log(dili.sing) // 'hello'
        var dili2 = new Dili()
        dili.art.push(3)
        console.log(dili2.art) // [1, 2, 3]
        console.log(dili instanceof Star) // true
      \n${suffix}`,
      text2: `${prefix}
        function Star(age) {
          this.age = age
          this.art = [1, 2]
        }
        Star.prototype.sing = 'hello'
        function Dili(age) {
          Star.call(this, age)
          this.age = age
        }
        // 验证
        var dili1 = new Dili(18)
        console.log(dili1) // {age: 18, art: [1, 2]}
        console.log(dili1.sing) // undefined
        var dili2 = new Star(20)
        dili1.art.push(3)
        console.log(dili2.art) // [1, 2]
        console.log(dili1 instanceof Star) // false
      \n${suffix}`,
      text3: `${prefix}
        function Star(age) {
          this.age = age
          this.art = [1, 2]
        }
        Star.prototype.sing = 'hello'
        function Dili(age) {
          Star.call(this, age)
          this.age = age
        }
        Dili.prototype = new Star(0)
        // 验证
        var dili1 = new Dili(18)
        console.log(dili1) // {age: 18, art: [1, 2]}
        console.log(dili1.sing) // 'hello'
        var dili2 = new Star(20)
        dili1.art.push(3)
        console.log(dili2.art) // [1, 2]
        console.log(dili1 instanceof Star) // true
      \n${suffix}`,
      text4: `${prefix}
        var Star = {
          age: 18,
          art: [1, 2]
        }
        function Dili(obj) {
          // 创建一个空对象copy
          // 此时copy.__proto__ = obj
          let copy = Object.create(obj)
          // 往拷贝的父类中添加属性和方法
          copy.getArt = function() {
              console.log(this.age)
          }
          // 返回新的拷贝
          return copy
        }
        // 验证
        var dili1 = new Dili(Star)
        console.log(dili1.age)
      \n${suffix}`,
      text5: `${prefix}
        function Star(age) {
          this.age = age
          this.art = [1, 2]
        }
        Star.prototype.sing = 'hello'
        function Dili(age) {
          Star.call(this, age)
          this.age = age
        }
        console.log(Object.create(Star.prototype))
        Dili.prototype = Object.create(Star.prototype)
        Dili.prototype.constructor = Dili
        // 验证
        var dili1 = new Dili(18)
        var dili2 = new Dili(20)
        console.log(dili1, dili2) // {age: 18, art: [1, 2]} {age: 20, art: [1, 2]}
        console.log(dili1.sing, dili2.sing) // hello hello
        dili1.art.push(3)
        console.log(dili2.art) // [1, 2]
        console.log(dili1 instanceof Star) // true
      \n${suffix}`,
    }
  }
}
</script>

<style lang="less" scoped>

</style>